
using System;
using System.Collections.Generic;
using System.Reflection;

namespace MxitAPI {
	
	/**
	 * Abstract base class that stores data for message request and response fields.
	 * Allows access to variables used in message sending through a string array.
	 * Sets varables obtained in a response from a string array.
	 */
	public abstract class ProtocolMessage {
		
		static log4net.ILog log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType );
		
		/// Unique command number sent to mxit to identify this message type.
		//private readonly int commandNum;
		
		/// Protocol message type.
		private readonly ProtocolMessageType protocolMessageType;
		
		/// Error code returned by MXit servers in response to a request.
		public ErrorCode ErrorCode { get; set; }
		
		/// Optional error message returned by MXit servers in response to a request.
		public string ErrorMessage { get; set; }
		
		/// The Handler for the response returned by the mxit servers. If its null, a new handler will be created to hanlde the response
		public MessageResponseHandler MessageResponseHandler { get; set; }		
		
		/*
		 * Sets the unique commandNum for a message.
		 */
		public ProtocolMessage( ProtocolMessageType protocolMessageType ) {
			this.protocolMessageType = protocolMessageType;			
		}
		
		/*
		 * Sets the unique commandNum for a message.
		 */
		public ProtocolMessage( int commandNum ) : this ( ( ProtocolMessageType ) commandNum ) {
			// this.commandNum = commandNum;			
		}
				
		
		/**
		 * Copy constructor
		 */
		public ProtocolMessage( ProtocolMessage protocolMessage ) : this( protocolMessage.ProtocolMessageType ) {
			
		}
		
		/*public int CommandNum {
			get { return commandNum; }
		}*/
		
		public ProtocolMessageType ProtocolMessageType {
			get { return protocolMessageType; }
		}
		
		/**
		 * Gets a list of all properties with a given attribute.
		 * 
		 * @param attributeType Type of attribute that properties must have.
		 * @return A list of all PropertyInfo objects in the message subclass that have the required attribute.
		 */
		private List<PropertyInfo> getPropertiesWithAttribute( Type attributeType ){
			
			List<PropertyInfo> resultPropInfo = new List<PropertyInfo>();
			
			// Get the child type.
			Type messageType = GetType();
			
			// Get all the properties of the child type.
			PropertyInfo[] propInfos = messageType.GetProperties();
			foreach ( PropertyInfo propInfo in propInfos ) {
				
				// Get all the attributes associated with this property.
				Attribute[] attributes = Attribute.GetCustomAttributes( propInfo ); //( Attribute[] ) propInfo.GetCustomAttributes( false );
				foreach ( Attribute attribute in attributes ) {
					if ( attribute.GetType() == attributeType ) {
						resultPropInfo.Add( propInfo );
					}
				}
			}
			
			return resultPropInfo;
		}
		
		/**
		 * Gets data that is needed for a request to the 
		 * mxit server as an ordered array of strings.
		 * 
		 * @return An ordered array of strings that should be sent to the MXit server.
		 */
		public string[] GetRequestData() {

			List<PropertyInfo> propInfos = getPropertiesWithAttribute( typeof( RequestDataAttribute ) );
			string[] requestData = new string[ propInfos.Count ];
			
			int requestDataIndex = 0;
			// Add the variable if it has been marked for sending to the Mxit server.
			for( int i = 0; i < propInfos.Count; ++i ){
				Object result = propInfos[i].GetGetMethod().Invoke( this, null );
				if( result == null ) {
					requestData[i] = "";
				}
				else {
					// Get the data to insert into the request
					object requestDataValue = propInfos[i].GetGetMethod().Invoke( this, null );
					
					// If this data is of type enum, do not send the string representing its type, but rather the underlying type (eg. integer)
					if ( requestDataValue is System.Enum ) {
						// Convert the enum to its underlying type
						requestDataValue = Convert.ChangeType( requestDataValue, ( ( System.Enum ) requestDataValue ).GetTypeCode() ); 
					}
					
					// If this is a boolean value, make it 0 for false and 1 for true
					if ( requestDataValue is bool ) {
						requestDataValue = ( ( bool ) requestDataValue ) ? 1 : 0;
					}
					
					// If this is a List, add each item in the list
					if ( requestDataValue is List<string> ) {
						log.Debug( "Adding a list of items to the request message" );
						List<string> list = ( List<string> ) requestDataValue;
						
						// Resize the request data array to fit this this list 
						// This is done because the request data array is only allocated 1 element per property in the protocol message
						Array.Resize( ref requestData, requestData.Length + list.Count - 1 );
						
						// Each item to a new element in the array
						foreach ( Object o in list ) {
							requestData[ requestDataIndex++ ] = o.ToString();
						}
						
					}
					// Otherwise add the data to a single element in the array as usual
					else
						requestData[ requestDataIndex++ ] = requestDataValue.ToString();
				}
			}
			
			//log.Debug( requestData );
			
			return requestData;
		}

		
		/**
		 * Sets the variables that are obtained from data 
		 * that the mxit server resturns after a request.
		 * 
		 * @param response A list of strings from MXit servers to populate the response variables in a message class and subclass.
		 */
		public virtual void SetResponse( string[][] response ) {
			
			//log.Debug( response );
			
			// Check that the minimum number of response variables where returned from the server.
			if ( response.Length < 2)
				throw new MxitException( "To few response fields returned from server." );
			
			// Check that the command number returned from the server is correct.
			if ( ProtocolMessageType != ( ProtocolMessageType ) Int32.Parse( response[0][0] ) )
				throw new MxitException( "The command number does not match the command number of this message." );
			
			// Get the error code and optional error message.
			ErrorCode = ( ErrorCode ) Int32.Parse( response[1][0] );
			if ( response[1].Length > 1 )
				ErrorMessage = response[1][1];
			
			// Check for a payload. May not exist in the case of an error code being returned by the mxit server
			if ( response.Length > 2 ) {
			
				// Get all properties that must be set using data that is obtained from the server.
				List<PropertyInfo> propInfo = getPropertiesWithAttribute( typeof( ResponseDataAttribute ) );
					
				// Set message properties using remaining response data from the server.
				for ( int i = 0; i < response[2].Length; ++i ) {
					//int propInfoIndex = i - 3;
					 
					// If there are more data returned by the server than there are variables to fill
					// ignore the extra data and assume the subclass has taken care of it
					if ( i >= propInfo.Count ) {
						continue;
						// throw new MxitException( "Too many response fields returned by the server." );
					}
					
					Object[] parameters = new Object[1];
					Type type = propInfo[i].PropertyType;
					
					// Convert the string returned by the server to its correct type.
					switch ( type.Name ) {
						case "String":
							parameters[0] = response[2][i];
							break;
						case "Int32":
							parameters[0] = response[2][i] == "" ? -1 : Int32.Parse( response[2][i] );
							break;
						case "Boolean":
							parameters[0] = response[2][i] == "0" ? false : true;
							break;
						case "Presence":
						case "Gender":
							parameters[0] = Int32.Parse( response[2][i] );
							break;
						default:
							throw new MxitException( type + " is not catered for." );
							//break;		
					}
					
					// Set the property with the converted data.
					propInfo[i].GetSetMethod().Invoke( this, parameters );
					
				}
			}
		}
		
		
	}
}
